#pragma once
#include <stdio.h>
#include <atomic>
#include <string>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>
#include <utility>
#include <thread>
#include <iostream>
#include <eclib3/ec_log.h>

static ec::udplog log;
static bool g_server = false;
static bool g_file = false;
static bool g_console = false;
static thread_local uint64_t tid;
static int console_control_level = -1;

struct noncopyable
{
protected:
    noncopyable() = default;
    virtual ~noncopyable() = default;

    noncopyable(const noncopyable &) = delete;
    noncopyable &operator=(const noncopyable &) = delete;
};

static uint64_t get_tid()
{
    pthread_t tid = pthread_self();
    uint64_t uid = 0;
    memcpy(&uid, &tid, std::min(sizeof(tid), sizeof(uid)));
    return uid;
}

#ifdef NDEBUG
#define hlog(level, ...)                                                                \
    do                                                                                  \
    {                                                                                   \
        if (level <= Logger::getLogger().getLogLevel())                                 \
        {                                                                               \
            Logger::getLogger().logv(level, __FILE__, __LINE__, __func__, __VA_ARGS__); \
        }                                                                               \
    } while (0)
#else
#define hlog(level, ...)                                                                \
    do                                                                                  \
    {                                                                                   \
        if (level <= Logger::getLogger().getLogLevel())                                 \
        {                                                                               \
            snprintf(0, 0, __VA_ARGS__);                                                \
            Logger::getLogger().logv(level, __FILE__, __LINE__, __func__, __VA_ARGS__); \
        }                                                                               \
    } while (0)

#endif

#define error(...) hlog(Logger::LERROR, __VA_ARGS__)
#define warn(...) hlog(Logger::LWARN, __VA_ARGS__)
#define msg(...) hlog(Logger::LMSG, __VA_ARGS__)
#define mor(...) hlog(Logger::LMOR, __VA_ARGS__)
#define dbg(...) hlog(Logger::LDBG, __VA_ARGS__)
#define fatal(...) hlog(Logger::LFATAL, __VA_ARGS__)
#define fatalif(b, ...)                        \
    do                                         \
    {                                          \
        if ((b))                               \
        {                                      \
            hlog(Logger::LFATAL, __VA_ARGS__); \
        }                                      \
    } while (0)
#define check(b, ...)                          \
    do                                         \
    {                                          \
        if ((b))                               \
        {                                      \
            hlog(Logger::LFATAL, __VA_ARGS__); \
        }                                      \
    } while (0)
#define exitif(b, ...)                         \
    do                                         \
    {                                          \
        if ((b))                               \
        {                                      \
            hlog(Logger::LERROR, __VA_ARGS__); \
            _exit(1);                          \
        }                                      \
    } while (0)

#define LEVELCOUNT 8 //共8个级别
struct Logger : private noncopyable
{
    //数值定义跟随logsrv
    enum LogLevel
    {
        LFATAL = 0,
        LERROR = 10,
        LUERR,
        LWARN = 20,
        LMSG = 30,
        LMOR = 31,
        LDBG = 40,
        LALL
    };
    Logger()
    {
        level_ = LMSG;
        lastRotate_ = time(NULL);
        rotateInterval_ = 86400;
        tzset();
        fd_ = -1;
        realRotate_ = lastRotate_;
    }
    ~Logger()
    {
        if (fd_ != -1)
        {
            close(fd_);
        }
    }
    void logv(int level, const char *file, int line, const char *func, const char *fmt...)
    {
#ifdef LOG_CLOSE
        return;
#endif
        if (tid == 0)
        {
            tid = get_tid();
        }
        if (level > level_)
        {
            return;
        }
        maybeRotate();
        char buffer[4 * 1024];
        char *p = buffer;
        char *limit = buffer + sizeof(buffer);

        struct timeval now_tv;
        gettimeofday(&now_tv, NULL);
        const time_t seconds = now_tv.tv_sec;
        struct tm t;
        localtime_r(&seconds, &t);

        std::string LogLevel;
        switch (level)
        {
        case LogLevel::LFATAL:
            LogLevel = "fatal";
            break;
        case LogLevel::LERROR:
            LogLevel = "error";
            break;
        case LogLevel::LUERR:
            LogLevel = "uerr";
            break;
        case LogLevel::LWARN:
            LogLevel = "warn";
            break;
        case LogLevel::LMSG:
            LogLevel = "msg";
            break;
        case LogLevel::LMOR:
            LogLevel = "mor";
            break;
        case LogLevel::LDBG:
            LogLevel = "dbg";
            break;
        default:
            LogLevel = "----";
            break;
        }

        p += snprintf(p, limit - p, "%04d/%02d/%02d-%02d:%02d:%02d.%06d pthread_t:%lx %s %s:%d ", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec,
                      static_cast<int>(now_tv.tv_usec), (long)tid, /*levelStrs_[level]*/ LogLevel.c_str(), file, line);
        va_list args;
        va_start(args, fmt);
        p += vsnprintf(p, limit - p, fmt, args);
        va_end(args);
        p = std::min(p, limit - 2);
        // trim the ending \n
        while (*--p == '\n')
        {
        }
        *++p = '\n';
        *++p = '\0';

        // int fd = fd_ == -1 ? 1 : fd_;
        // int err = ::write(fd, buffer, p - buffer);
        int err;
        if (g_file)
            err = ::write(fd_, buffer, p - buffer);

        if (g_console)
        {
            if (console_control_level == -1 || console_control_level == level)
                err = ::write(1, buffer, p - buffer);
        }

        if (err != p - buffer)
        {
            fprintf(stderr, "write log file %s failed. written %d errmsg: %s\n", filename_.c_str(), err, strerror(errno));
        }
        if (level <= LERROR)
        {
            syslog(LOG_ERR, "%s", buffer + 27);
        }
        if (level == LFATAL)
        {
            fprintf(stderr, "%s", buffer);
            assert(0);
        }

        if (g_server)
            log.add(level, "%s", buffer);
    }

    void setFileName(const std::string &filename)
    {
        int fd = open(filename.c_str(), O_APPEND | O_CREAT | O_WRONLY | O_CLOEXEC, DEFFILEMODE);
        if (fd < 0)
        {
            fprintf(stderr, "open log file %s failed. msg: %s ignored\n", filename.c_str(), strerror(errno));
            return;
        }
        filename_ = filename;
        if (fd_ == -1)
        {
            fd_ = fd;
        }
        else
        {
            int r = dup2(fd, fd_);
            fatalif(r < 0, "dup2 failed");
            close(fd);
        }
    }

    int setLogLevel(const std::string &level)
    {
        LogLevel ilevel = LMSG;
        // for (size_t i = 0; i < sizeof(levelStrs_) / sizeof(const char *); i++)
        // {
        //     if (strcasecmp(levelStrs_[i], level.c_str()) == 0)
        //     {
        //         ilevel = (LogLevel)i;
        //         break;
        //     }
        // }

        if (strcasecmp(levelStrs_[0], level.c_str()) == 0)
        {
            ilevel = LogLevel::LFATAL;
        }
        else if (strcasecmp(levelStrs_[1], level.c_str()) == 0)
        {
            ilevel = LogLevel::LERROR;
        }
        else if (strcasecmp(levelStrs_[2], level.c_str()) == 0)
        {
            ilevel = LogLevel::LUERR;
        }
        else if (strcasecmp(levelStrs_[3], level.c_str()) == 0)
        {
            ilevel = LogLevel::LWARN;
        }
        else if (strcasecmp(levelStrs_[4], level.c_str()) == 0)
        {
            ilevel = LogLevel::LMSG;
        }
        else if (strcasecmp(levelStrs_[5], level.c_str()) == 0)
        {
            ilevel = LogLevel::LMOR;
        }
        else if (strcasecmp(levelStrs_[6], level.c_str()) == 0)
        {
            ilevel = LogLevel::LDBG;
        }
        else if (strcasecmp(levelStrs_[7], level.c_str()) == 0)
        {
            ilevel = LogLevel::LALL;
        }

        setLogLevel(ilevel);
        return ilevel;
    }
    void setLogLevel(LogLevel level) { level_ = std::min(LALL, std::max(LFATAL, level)); }

    LogLevel getLogLevel() { return level_; }
    const char *getLogLevelStr() { return levelStrs_[level_]; }
    int getFd() { return fd_; }

    void adjustLogLevel(int adjust) { setLogLevel(LogLevel(level_ + adjust)); }
    void setRotateInterval(long rotateInterval) { rotateInterval_ = rotateInterval; }
    static Logger &getLogger()
    {
        static Logger logger;
        return logger;
    }

private:
    void maybeRotate()
    {
        time_t now = time(NULL);
        if (filename_.empty() || (now - timezone) / rotateInterval_ == (lastRotate_ - timezone) / rotateInterval_)
        {
            return;
        }
        lastRotate_ = now;
        long old = realRotate_.exchange(now);
        //如果realRotate的值是新的，那么返回，否则，获得了旧值，进行rotate
        if ((old - timezone) / rotateInterval_ == (lastRotate_ - timezone) / rotateInterval_)
        {
            return;
        }
        struct tm ntm;
        localtime_r(&now, &ntm);
        char newname[4096];
        snprintf(newname, sizeof(newname), "%s.%d%02d%02d%02d%02d", filename_.c_str(), ntm.tm_year + 1900, ntm.tm_mon + 1, ntm.tm_mday, ntm.tm_hour, ntm.tm_min);
        const char *oldname = filename_.c_str();
        int err = rename(oldname, newname);
        if (err != 0)
        {
            fprintf(stderr, "rename logfile %s -> %s failed msg: %s\n", oldname, newname, strerror(errno));
            return;
        }
        int fd = open(filename_.c_str(), O_APPEND | O_CREAT | O_WRONLY | O_CLOEXEC, DEFFILEMODE);
        if (fd < 0)
        {
            fprintf(stderr, "open log file %s failed. msg: %s ignored\n", newname, strerror(errno));
            return;
        }
        dup2(fd, fd_);
        std::thread t([=]
                      {
        usleep(200 * 1000); // 睡眠200ms，参考leveldb做法
        close(fd); });
        t.detach();
    }
    int fd_;
    LogLevel level_;
    long lastRotate_;
    std::atomic<int64_t> realRotate_;
    long rotateInterval_;
    std::string filename_;

public:
    static constexpr char *levelStrs_[LEVELCOUNT + 1] = {
        (char *)"FATAL",
        (char *)"ERROR",
        (char *)"UERR",
        (char *)"WARN",
        (char *)"MSG",
        (char *)"MOR",
        (char *)"DBG",
        (char *)"ALL",
    };
};

#define setloglevel(l) Logger::getLogger().setLogLevel(l)
#define setlogfile(n) Logger::getLogger().setFileName(n)

static void initLogConf(bool server, bool file, bool console, std::string level, std::string serveraddr, std::string clientid, int rotatesize)
{
    g_server = server;
    g_file = file;
    g_console = console;
    log.open((serveraddr + clientid).c_str());
    setlogfile("log.txt");
    log.setlevel(setloglevel(level));

    std::thread console_control([]()
                                {
                            while (true)
                            {
                                char buf[1024] = {0};
                                std::cin >> buf;
                                if (strcasecmp("error", buf) == 0)
                                {
                                    console_control_level = Logger::LogLevel::LERROR;
                                }
                                else if (strcasecmp("warn", buf) == 0)
                                {
                                    console_control_level = Logger::LogLevel::LWARN;
                                }
                                else if (strcasecmp("msg", buf) == 0)
                                {
                                    console_control_level = Logger::LogLevel::LMSG;
                                }
                                else if (strcasecmp("mor", buf) == 0)
                                {
                                    console_control_level = Logger::LogLevel::LMOR;
                                }
                                else if (strcasecmp("dbg", buf) == 0)
                                {
                                    console_control_level = Logger::LogLevel::LDBG;
                                }
                            } });
    console_control.detach();
}
